package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.ItemClient;
import com.leyou.item.pojo.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms.Bucket;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Autowired
    ElasticsearchTemplate esTemplate;
    @Autowired
    GoodsRepository goodsRepository;
    @Autowired
    ItemClient itemClient;

    /**
     * 把一个Spu转为一个Goods对象
     *
     * @param spuDTO
     * @return Goods对象
     */
    public Goods buildGoods(SpuDTO spuDTO) {

        Goods goods = new Goods();
        goods.setId(spuDTO.getId());
        goods.setCategoryId(spuDTO.getCid3());//只要第三个 直接上级商品名
        goods.setBrandId(spuDTO.getBrandId());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        goods.setSubTitle(spuDTO.getSubTitle());

        //封装all查询时用到的属性
        String all = spuDTO.getName() + spuDTO.getBrandName() + spuDTO.getCategoryName();
        goods.setAll(all);

        //封装skus 这里最终转换为list<Map>格式的json数据
        List<SkuDTO> skus = itemClient.findSkusBySpuId(spuDTO.getId());
        ArrayList<Object> list = new ArrayList<>();
        skus.forEach(sku -> {
            Map<String, Object> skuMap = new HashMap<>();
            skuMap.put("id", sku.getId());
            skuMap.put("title", sku.getTitle());
            skuMap.put("price", sku.getPrice());
            skuMap.put("image", StringUtils.substringBefore(sku.getImages(), ","));
            list.add(skuMap);
        });
        goods.setSkus(JsonUtils.toString(list));
        //封装价格
        Set<Long> set = skus.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());
        goods.setPrice(set);
        //--------------------------------------
        //封装参数规格
        Map<String, Object> specs = new HashMap<>();
        //都拿第三级分类来查询
        //规格参数的key
        List<SpecParamDTO> specParams = itemClient.findSpecParamsByGid(null, spuDTO.getCid3(), true);
        //规格参数的value 某个spu的"规格"属性全在里面了 此页面全部理解为spu,即使是特有属性,也都是多个特有的集合
        SpuDetailDTO spuDetail = itemClient.findSpuDetailBySpuId(spuDTO.getId());
        //通用的规格参数值
        Map<Long, Object> genericSpec = JsonUtils.toMap(spuDetail.getGenericSpec(), Long.class, Object.class);
        //特有的规格参数值
        Map<Long, List<String>> specialSpec = JsonUtils.nativeRead(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });
        specParams.forEach(specParam -> {
            // 获取规格参数的名称
            String key = specParam.getName();
            // 获取规格参数值
            Object value = null;
            //判断是否是通用属性
            if (specParam.getGeneric()) {
                value = genericSpec.get(specParam.getId());
            } else {
                // 特有规格 封装的value是[xx色,xx色,xx色]
                value = specialSpec.get(specParam.getId());
            }
            // 判断是否是数字类型
            if (specParam.getNumeric()) {
                // 是数字类型，分段
                value = chooseSegment(value, specParam);
            }
            // 添加到specs
            specs.put(key, value);
        });
        goods.setSpecs(specs);
        return goods;
    }

    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    public PageResult<GoodsDTO> findByKey(SearchRequest searchRequest) {
        // 0.健壮性判断
        String key = searchRequest.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        //1构建原生搜索器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2组织条件
        //控制source字段数量
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));
        //搜索条件                              进行分词搜索 matchall会匹配全部       若干个关键词必须全部匹配到才展示
        queryBuilder.withQuery(boolBaseQuery(searchRequest));
        //分页条件
        int page = searchRequest.getPage() - 1;//page是从0开始的
        int size = searchRequest.getSize();
        queryBuilder.withPageable(PageRequest.of(page, size));
        //3搜索结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //解析结果返回到页面
        long totalElements = result.getTotalElements();
        int totalPages = result.getTotalPages();
        List<Goods> list = result.getContent();
        //转换DTO
        List<GoodsDTO> goodsDTOS = BeanHelper.copyWithCollection(list, GoodsDTO.class);

        return new PageResult<>(totalElements, totalPages, goodsDTOS);
    }

//    private QueryBuilder getBaseQuery(SearchRequest searchRequest) {
//        return QueryBuilders.matchQuery("all", searchRequest.getKey()).operator(Operator.AND);
//    }

    private QueryBuilder boolBaseQuery(SearchRequest searchRequest) {
        //构建布尔查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        //构建基本的match查询
        queryBuilder.must(QueryBuilders.matchQuery("all", searchRequest.getKey()).operator(Operator.AND));
        //构建过滤条件
        Map<String, Object> filters = searchRequest.getFilter();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Entry<String, Object> entry : filters.entrySet()) {
                //获取过滤条件的key
                String key = entry.getKey();
                //规格参数的key要加spec.
                if ("分类".equals(key)) {
                    key = "categoryId";
                }
                if ("品牌".equals(key)) {
                    key = "brandId";
                } else {
                    key = "specs." + key;
                }
                //
                Object value = entry.getValue();
                //添加过滤条件
                queryBuilder.filter(QueryBuilders.termQuery(key,value));
            }
        }
        return queryBuilder;
    }

    public Map<String, List<?>> queryFilter(SearchRequest searchRequest) {
        //创建过滤项集合
        LinkedHashMap<String, List<?>> filterList = new LinkedHashMap<>();
        //构建原生搜索器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //获取搜索条件
        QueryBuilder baseQuery = boolBaseQuery(searchRequest);
        queryBuilder.withQuery(baseQuery);//也是查询全部的 才可以进行聚合
        queryBuilder.withPageable(PageRequest.of(0, 1));
        // 显示空的source
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());

        //查询条件已经设置完毕 接下来进行聚合条件
        //分类聚合
        String categoryAgg = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        //品牌聚合
        String brandAgg = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));

        //查询并且解析结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = result.getAggregations();

        //获取品牌聚合结果
        LongTerms bTerms = aggregations.get(brandAgg);
        handleBrandAgg(bTerms, filterList);

        //获取分类聚合结果
        LongTerms cTerms = aggregations.get(categoryAgg);
        List<Long> idList = handleCategoryAgg(cTerms, filterList);
        if (idList != null && idList.size() == 1) {
            handlerParamsMap(idList.get(0), baseQuery, filterList);
        }
        return filterList;
    }

    private void handlerParamsMap(Long aLong, QueryBuilder baseQuery, LinkedHashMap<String, List<?>> filterList) {
        //查询出唯一分类下的需要过滤的规格参数名称
        List<SpecParamDTO> specParamDTOS = itemClient.findSpecParamsByGid(null, aLong, true);
        //
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(baseQuery);//只有這一行是查詢操作(all),其他都是显示有關的操作
        queryBuilder.withPageable(PageRequest.of(0, 1));
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());
        for (SpecParamDTO specParamDTO : specParamDTOS) {//设置了多次聚合条件,每个带specs.的规格参数都设置了一下
            String name = specParamDTO.getName();           //聚合名称(自定义)↓     //聚合字段↓
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }
        //查询并获取结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = result.getAggregations();
        //解析结果
        for (SpecParamDTO specParamDTO : specParamDTOS) {
            String name = specParamDTO.getName();
            StringTerms terms = aggregations.get(name);
            List<String> paramValues = terms.getBuckets()
                    .stream()
                    .map(StringTerms.Bucket::getKeyAsString)
                    .filter(org.apache.commons.lang3.StringUtils::isNoneEmpty)
                    .collect(Collectors.toList());
            filterList.put(name, paramValues);
        }
    }

    private void handleBrandAgg(LongTerms bTerms, LinkedHashMap<String, List<?>> filterList) {
        List<Long> list = bTerms.getBuckets().stream()
                .map(Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        List<BrandDTO> brandDTOS = itemClient.queryBrandByIds(list);//参数为null也会报feign的错!!!
        filterList.put("品牌", brandDTOS);
    }

    private List<Long> handleCategoryAgg(LongTerms cTerms, LinkedHashMap<String, List<?>> filterList) {
        List<Long> list = cTerms.getBuckets().stream()
                .map(Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        List<CategoryDTO> categoryDTOS = itemClient.queryCategoryByIds(list);
        filterList.put("分类", categoryDTOS);
        return list;//之前只是作为查询的条件,现在将它返回
    }

    public void saveItem(Long id) {
        //从数据库中查出来
        SpuDTO spuDTO = itemClient.querySpuById(id);
        Goods goods = buildGoods(spuDTO);
        //保存到索引库
        goodsRepository.save(goods);
    }

    public void deleteItem(Long id) {
        goodsRepository.deleteById(id);
    }
}